package simpleftp.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import java.util.concurrent.Callable;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import simpleftp.client.control.Command;

/**
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files. 
 */
public class FTPServer implements Callable<Void> {
	private static boolean DEBUG = false;
	private String response, user;
	private int validations;
	private File f;
	private StringBuilder sb;
	private int portnum1, portnum2;
	private Properties p;
	private ServerSocket dl;
	private Socket dl2;
	private FileInputStream fis;
	private Socket socket = null;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	/// TO BE COMPLETED - 1 LINE
	private ObjectOutputStream oos = null;
	private JSONObject jsonObject;
	private String jsonRepresentation;
	
	/**
     * Constructor of FTPServer
     */
	public FTPServer(Socket s) {
		socket = s;
		validations = 0;
		try {
			p = new Properties();
			fis = new FileInputStream("users.properties");
			p.load(fis);
			try {
				reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
				writer = new BufferedWriter(new OutputStreamWriter(
						socket.getOutputStream()));
				/// TO BE COMPLETED - 1 LINE
				oos = new ObjectOutputStream(s.getOutputStream());
				
				sendLine("220 ");
				validations++;
				f = new File(System.getProperty("user.dir"));	
				
				System.out.println("FTP Server : new client from " + socket.getInetAddress().getHostAddress());
			
				// Creating a JSON String Representation of the hierarchy				
				jsonRepresentation = createJSON(f, 1);
				jsonRepresentation = "{\n" + "\"" + f.getName() + "\":\n\t[\n" + jsonRepresentation + "\t], \"folder\":true, \"name\":\"" + f.getName() + "\"\n}";
				
				// Creating the JSON Object
				JSONParser JSONparser = new JSONParser();
				Object object = null;
				try 
				{
					object = JSONparser.parse(jsonRepresentation);
				} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				}
				jsonObject = (JSONObject) object;


				
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		} catch (IOException ioex) {
			System.out.println("Error: Properties File didn't load");
			try {
				sendLine("error ");
			} catch (IOException e) {
				e.printStackTrace();
			}
			ioex.printStackTrace();
		}
	}

	/**
     * Main task execution (listening for instruction and calling functions)
     */
	public Void call() {
		try {
			response = readLine();
			if (response.startsWith("USER")) {
				if (p.containsKey(response.substring(5))) {
					user = response.substring(5);
					System.out.println("Server knows user " + response);
					sendLine("331 ");
					validations++;
				} else {
					System.out.println("Server does no know user " + response);
					sendLine("error ");
				}
			}
			response = readLine();
			if (response.startsWith("PASS")) {
				if (p.get((String) user).equals(response.substring(5))) {
					System.out.println("Server validates pass " + response);
					sendLine("230 ");
					validations++;
				} else {
					sendLine("error ");
				}
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			try {
				sendLine("error ");
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

		while (validations == 3) {
			try {
				response = readLine();

				switch (Command.valueOf(response.split(" ")[0])) {

				case PWD:
					System.out.println("Case PWD");
					sendLine("257 " + System.getProperty("user.dir"));
					break;

				case LIST:
					System.out.println("Case LIST");
					sb = new StringBuilder("200 ");
					String[] fileNames = f.list();
					for (String fileName : fileNames) {
						sb = sb.append(fileName + "|");
					}
					sendLine(sb.toString());
					
			//////////////////////////////////////////////////////////////////////
			//// JSON VERSION -> comment / uncomment this fonction to switch ////
			/////////////////////////////////////////////////////////////////////
					//sendSerialisable(f);
					sendSerialisableJSONObject(jsonObject);
					break;

				case CWD:
					System.out.println("Case CWD");
					System.out.println("CWD");
					System.setProperty("user.dir", response.substring(4));
					System.out.println("CWD new current directory:"
							+ System.getProperty("user.dir").toString());
					f = new File(System.getProperty("user.dir"));
					sendLine("250 ");
					break;

				case PASV:
					System.out.println("Case PASV");
					pasv();
					break;

				case STOR:
					System.out.println("Case STOR");
					stor();
					break;
				
				case RETR:
					System.out.println("Case RETR");
					retr();
					break;

				case QUIT:
					System.out.println("Case QUIT");
					System.out.println("QUIT :Goodbye");
					validations = 0;
					socket.close();
					break;

				default:
					System.out.println("Case default");
					System.out.println("Unknown Command.");
					sendLine("error");
					break;

				}
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {

					e1.printStackTrace();
				}
				e.printStackTrace();
			}

		}
		Void Void = null;
		return Void;
	}
	
	/**
     * Sending a serialisable File Object over the network
     */
	private void sendSerialisable(File f)
	{
		try {
			oos.writeObject(f);
			//encoder.writeObject(FTPClientWindow.)
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
     * Sending a serialisable JSON Object over the network
     */
	private void sendSerialisableJSONObject(JSONObject j)
	{
		try {
			oos.writeObject(j);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
     * Creating a JSON Representation (String) of the hierarchy folders
     */
	private String createJSON(File root, int level)
	{
		
		String jsonString = "";
		
		File[] list = root.listFiles();
		int index = 0;
		
		if(list == null)
		{
			System.out.println("NULL");
			return "";
		}
		else 
		{
		   for(File nom : list)
		   {
		   
		   	if(nom.isDirectory())
		   	{
			   	
			   	for(int i=0;i<level;i++) jsonString += "\t";
			   	
			   	jsonString += "{\"" + nom.getName() + "\":" + "\n";
			   	
			   	for(int i=0;i<level;i++) jsonString += "\t";
			   	
			   	jsonString += "[\n";
			    jsonString += this.createJSON(nom , (++level));
			           
			    for(int i=0;i<level-1;i++) jsonString += "\t";
			    
			    jsonString += "], \"folder\":true, \"name\":\"" + nom.getName() + "\"}";
			    
			    if(index != (list.length-1)) jsonString += ",";
			    
			    jsonString += "\n";
			    --level;
		   	}
		   	else
		   	{
			   	for(int i=0;i<level;i++) jsonString += "\t";
			   	
			   	jsonString += "{\"name\":";
			   	jsonString += "\"" + nom.getName() + "\", \"folder\":false}";
			   	
			   	if(index != (list.length-1)) jsonString += ",";
			   	
			   	jsonString += "\n";
			 }
			   	index++;
		   }
		}
		return jsonString;
	}
	/**
     * Sends a raw command to the FTP server.
     */

	private void sendLine(String line) throws IOException {
		if (socket == null) {
			throw new IOException("FTPServer is not connected.");
		}
		try {
			writer.write(line + "\r\n");
			writer.flush();
			if (DEBUG) {
				System.out.println("> " + line);
			}
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	/**
     * Reading line and displaying it
     */
	private String readLine() throws IOException {
		String line = reader.readLine();
		if (DEBUG) {
			System.out.println("< " + line);
		}
		return line;
	}
	
	/**
     * Writing data on HDD from a buffer
     */

	private void stor() 
	{
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			OutputStream os = new FileOutputStream(new File(
					System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			InputStream is = dl2.getInputStream();
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) 
			{
				os.write(buffer, 0, bytesRead);
			}
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("STOR " + response.split(" ")[1]
					+ " : File received");
		} catch (IOException e) {
			try 
			{
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	/**
     * Uploading function
     */
	private void retr() 
	{
		System.out.println("RETR Called");
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			OutputStream os = new FileOutputStream(new File(response.substring(5)));
			InputStream is = dl2.getInputStream();
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) 
			{
				os.write(buffer, 0, bytesRead);
			}
			os.flush();
			os.close();
			is.close();
			sendLine("227 ");
			dl.close();
			dl2.close();
			System.out.println("RETR " + response.split(" ")[1]
					+ " : File transfered");
		} catch (IOException e) {
			try 
			{
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}


	private void pasv() {
		try {
			dl = new ServerSocket(0);
			portnum1 = dl.getLocalPort() / 256;
			portnum2 = dl.getLocalPort() % 256;
			sendLine("227 ("
					+ InetAddress.getLocalHost().getHostAddress()
							.replace('.', ',') + "," + portnum1 + ","
					+ portnum2 + ")");
			System.out.println("PASV success");
		} catch (IOException e) {
			try {
				sendLine("error ");
				System.out.println("pasv error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	public static void setDebug(boolean b) {
		DEBUG = b;
	}
}
